//
// Created by dave on 18-7-31.
//

#pragma once
#include <mutex>
#include <queue>
#include <vector>
#include <condition_variable>


template<typename T>
class blocking_queue {

public:
    blocking_queue(int capacity): capacity_(capacity) { }

    bool offer(T const& value) {
        {
            std::unique_lock<std::mutex> lock(this->mutex_);
            if (queue_.size() < capacity_) {
                queue_.push(value);
            } else {
                return false;
            }
        }
        not_empty_.notify_one();
        return true;
    }

    void push(T const& value) {
        {
            std::unique_lock<std::mutex> lock(mutex_);
            not_full_.wait(lock, [this]{ return queue_.size() < capacity_; });
            queue_.push(value);
        }
        not_empty_.notify_one();
    }

    T peek() {
        {
            std::unique_lock<std::mutex> lock(mutex_);
            if (queue_.size() > 0) {
                T rc(std::move(queue_.front()));
                queue_.pop();
                return rc;
            } else {
                return T();
            }
        }
    }

    T pop() {
        std::unique_lock<std::mutex> lock(mutex_);
        not_empty_.wait(lock, [this]{ return queue_.size() > 0; });
        T rc(std::move(queue_.front()));
        queue_.pop();
        return rc;
    }

    std::vector<T> drain(int max_size) {
        std::unique_lock<std::mutex> lock(mutex_);
        std::vector<T> v;
        while (!queue_.empty() && v.size() < max_size) {
            v.push_back(std::move(queue_.front()));
            queue_.pop();
        }
        return v;
    }

private:
    int capacity_;
    std::mutex mutex_;
    std::condition_variable not_empty_;
    std::condition_variable not_full_;
    std::queue<T> queue_;
};